#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <sys/stat.h>

#include <sys/ioctl.h>
#include <linux/unistd.h>
#include <sys/ioctl.h>


#include <linux/moto_accy.h>
#include <linux/power_ic.h>
#include <linux/ezxusbd.h>
#include <linux/pcap_rtc.h>

#include <syslog.h>

#define log(d,s,args...) syslog(d,s,##args)
#define dbg(s,args...)   log(LOG_DEBUG,s,##args)
#define err(s,args...)   log(LOG_ERR  ,s,##args)

// battery voltage
enum {
  VOLTAGE_MIN,
  VOLTAGE_405 = VOLTAGE_MIN,
  VOLTAGE_410,
  VOLTAGE_415,
  VOLTAGE_420,
  VOLTAGE_425,
  VOLTAGE_430,
  VOLTAGE_380,
  VOLTAGE_450,
  VOLTAGE_MAX = VOLTAGE_450
};

// charger current
enum {
  CURRENT_MIN,
  CURRENT_0 = CURRENT_MIN,
  CURRENT_100,
  CURRENT_200,
  CURRENT_300,
  CURRENT_400,
  CURRENT_500,
  CURRENT_600,
  CURRENT_700,
  CURRENT_800,
  CURRENT_900,
  CURRENT_1000,
  CURRENT_1100,
  CURRENT_1200,
  CURRENT_1300,
  CURRENT_1800,
  CURRENT_MAX
};


// control fds
int usb,accy,power;

// TODO: config
#define USB_CHARGE false
bool charging;

int saved_time = 0;
int saved_time_last = 0;

int  cable,headset;

// cpu speed

short flip = -1;

// set correct time from PCAP2
// need to call after each sleep state
int time_fix() {


  struct timeval tv = { 0, 0 };
  struct timezone tz;

  // read timezone
  gettimeofday(&tv,&tz);

  // read time from pcap
  int pcap_rtc = open("/dev/pcap_rtc", O_RDWR);
  int ret = ioctl(pcap_rtc, PCAP_RTC_IOC_GET_TIME, &tv);
  close(pcap_rtc);

  if (ret)
    err("Agggrrhhh... PCAP_RTC_IOC_GET_TIME failed\n");
  else
    settimeofday(&tv,&tz); // set system time

  return tv.tv_sec;

}

// set correct time from PCAP2
// need to call after each sleep state
void time2pcap(struct timeval *tv ) {


  // read time from pcap
  int pcap_rtc = open("/dev/pcap_rtc", O_RDWR);
  int ret = ioctl(pcap_rtc, PCAP_RTC_IOC_SET_TIME, tv);
  close(pcap_rtc);


}


// motorola 2.4 kernel charge current and voltage setting
int set_charge_current(int current) {
  return ioctl(power,POWER_IC_IOCTL_CHARGER_SET_CHARGE_CURRENT,current);
}

int set_trickle_current(int trickle) {
  return ioctl(power,POWER_IC_IOCTL_CHARGER_SET_TRICKLE_CURRENT,trickle);
}

int set_charge_voltage(int voltage) {
  return ioctl(power,POWER_IC_IOCTL_CHARGER_SET_CHARGE_VOLTAGE,voltage);
}

// get battery charge level
int battery() {
  POWER_IC_ATOD_REQUEST_BATT_AND_CURR_T info;
  info.timing = 0;

  ioctl (power,POWER_IC_IOCTL_ATOD_BATT_AND_CURR,&info);
  return info.batt_result;
}

int battery_percent(int raw) {
  if (!raw)
    raw = battery();

  return (raw - 490 ) * 10 / 22  ;

}

void charge_alarm(int sig);

// set charge current, battery voltage and manage marker files
void charger(int current, int voltage) {
  int ret;
  ret = set_trickle_current(0);
  dbg("trickle current set: %d, ret: %d\n",current,ret);
  ret = set_charge_current(current);
  dbg("current set: %d, ret: %d\n",current,ret);
  set_charge_voltage(voltage);
  dbg("voltage set: %d, ret: %d\n",voltage,ret);

  if (current)  {

    alarm(30);
    signal(SIGALRM, charge_alarm);


    charging = true;

  } else {

    charging = false;
  }

}
// usb mode switching
void usb_mode(int mode) {
  ioctl(usb,MOTUSBD_IOCTL_SET_USBMODE,&mode);
}

// handle cable plug event
void plug_cable(int type) {

  if (type < MOTO_ACCY_TYPE_HEADSET_MONO)
    cable = type;
  else
    headset = type;

  switch (type) {

    case MOTO_ACCY_TYPE_CARKIT_MID:
    case MOTO_ACCY_TYPE_CHARGER_MID_MPX:
    case MOTO_ACCY_TYPE_CHARGER_MID:

        dbg("charger attached\n");
        charger(CURRENT_1300,VOLTAGE_425);

      break;
    case MOTO_ACCY_TYPE_CABLE_USB:

        dbg("usb cable attached\n");

        if (USB_CHARGE)
          charger(CURRENT_200,VOLTAGE_425);
        else
          charger(CURRENT_0,VOLTAGE_MIN);

        alarm(0);

        usb_mode(USB_STATUS_NET);

      break;

    // headsets handled by script and qtopia
    case MOTO_ACCY_TYPE_HEADSET_MONO:
    case MOTO_ACCY_TYPE_HEADSET_STEREO:
    case MOTO_ACCY_TYPE_HEADSET_EMU_MONO:
    case MOTO_ACCY_TYPE_HEADSET_EMU_STEREO:
    case MOTO_ACCY_TYPE_3MM5_HEADSET_STEREO:
    case MOTO_ACCY_TYPE_3MM5_HEADSET_STEREO_MIC:
      break;
    default:
        dbg("attached cable %d\n",type);
    }

    char cmd[30];
    sprintf(cmd,"/etc/motod/cable plug %d",type);
    system(cmd);

}
// handle cable unplug
void unplug_cable(int type) {

  if (type < MOTO_ACCY_TYPE_HEADSET_MONO)
    cable = 0;
  else
    headset = 0;

  alarm(0);

  switch (type) {

    case MOTO_ACCY_TYPE_CARKIT_MID:
    case MOTO_ACCY_TYPE_CHARGER_MID_MPX:
    case MOTO_ACCY_TYPE_CHARGER_MID:

      dbg("charger detached\n");
      charger(CURRENT_0,VOLTAGE_MIN);


      break;
    case MOTO_ACCY_TYPE_CABLE_USB:

        dbg("usb cable detached\n");
        if (USB_CHARGE)
          charger(CURRENT_0,VOLTAGE_MIN);

        usb_mode(-1);

      break;

    case MOTO_ACCY_TYPE_HEADSET_MONO:
    case MOTO_ACCY_TYPE_HEADSET_STEREO:
    case MOTO_ACCY_TYPE_HEADSET_EMU_MONO:
    case MOTO_ACCY_TYPE_HEADSET_EMU_STEREO:
    case MOTO_ACCY_TYPE_3MM5_HEADSET_STEREO:
    case MOTO_ACCY_TYPE_3MM5_HEADSET_STEREO_MIC:
        break;

    default:
        dbg("detached cable %d\n",type);
    }

    char cmd[30];
    sprintf(cmd,"/etc/motod/cable unplug %d",type);
    system(cmd);
}

void process_plugged_cables()
{
  // already connected cables
  unsigned long int connected_cables;
  ioctl(accy, MOTO_ACCY_IOCTL_GET_ALL_DEVICES, &connected_cables);
  while (connected_cables){
    int n = generic_ffs(connected_cables) - 1;
    dbg("ha! cable %d was connected\n",n);
    plug_cable(n);

    connected_cables &= ~(1 << (n));
  }
}

// check if battery charge complete to remove charge current
void charge_alarm(int sig) {

  int b = battery();

  dbg("charge alarm charging flag: %d,  battery charge level: %d\n", charging, b );

  if ( charging && ( b > 750 ) ) {

    charger(0,0);
    set_trickle_current(8);
    dbg("charging complete, truckle current set\n");

  } else if (  charging && ( b > 720 ) ) {
    alarm(10);
    dbg("no-no.. charge level over 720, waiting 10 seconds\n");
  } else {
    dbg("no.. charge level below 720, waiting 30 seconds\n");
    alarm(30);
  }
}


int version(int l) {
  printf("MotoD version %s EZX hardware support daemon for legacy kernel\n",MOTOD_VER);

  if (l > 2) {
    printf("No Tatiana found. Self-destruct in five seconds. Buy new phone now.\n");
    return 1;
  } else {
    printf("Have a nice day!\n");
    return 0;
  }

}

int help() {
  printf("Usage: motod [-V] [-n]\n");
  return 0;
}

int main(int argc, char *argv[]) {

  // parse command line
  // -V - show version and exit
  // -n - dont detach from terminal
  // any other - show help

  if (argc > 1) { // arguments passed

    switch(argv[1][1]) {
      case 'V': return version(strlen (argv[1]) ); break;
      case 'n': printf("Not detaching\n"); break;
      default:  return help();
    }

  } else { // if no arguments
    if ( fork() )
      return 0;

    char logfile[40];
    sprintf(logfile,"/var/log/motod/%d.log",time(NULL) );

    freopen("/dev/null", "w",stdout);
    freopen("/dev/null", "w",stderr);
    freopen("/dev/null", "r",stdin);

    openlog("motod", LOG_PID, LOG_DAEMON);
    dbg("started");

  }


  int pid = open("/var/run/motod.pid",O_CREAT | O_RDWR | O_TRUNC);
  char cpid[11];
  int n = sprintf(cpid,"%d",(int)getpid() );
  write(pid,&cpid,n);
  close(pid);

  time_fix();

  fd_set devs, devs_back;

  usb   = open("/dev/motusbd",  O_RDWR);
  accy  = open("/dev/accy",     O_RDWR);
  power = open("/dev/power_ic", O_RDWR);

  if (usb < 0) {
    err("cant open usb\n");
    return 1;
  }



  if (accy < 0) {
    err("cant open accy\n");
    return 1;
  }

  if (power < 0){
    err("cant open power\n");
    return 1;
  }

  // set alarm and set handler to sleep
  // if charge cable connected - set handler to charge

  usb_mode(-1);   
  process_plugged_cables();

  FD_ZERO(&devs);

  FD_SET(accy,  &devs);

  devs_back = devs;

    // check for device events
  while (1) {
    devs = devs_back;
    select(FD_SETSIZE,&devs,NULL,NULL,NULL);

    if(FD_ISSET(accy,&devs) ) {
      unsigned long int event;
      read(accy, &event, 4);
      if (event & 0x80000000)
        plug_cable  (0x7fffffff & event);
      else
        unplug_cable(0x7fffffff & event);
    }
  }
}
